// Copyright (C) 1998-2008, Sumisho Computer Systems Corp. All Rights Reserved.

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @author Hitoshi Okada
 */

package com.curlap.orb.security;

import org.apache.commons.beanutils.MethodUtils;
import org.apache.commons.logging.LogFactory;

/**
 * AccessControlFilter
 *   NOTE: default is "deny all"  
 */
public class AccessControlFilter 
{
    private static AccessControlFilter instance = null;
    private AccessControlAllow[] allowAccessControlList = null;

    // TODO: For performance, we should create allowedCacheList or other ways.
    // TODO: method filter.
    
    // constructors
    protected AccessControlFilter(AccessControlAllow[] accessControlList)
    {
        allowAccessControlList = accessControlList; 
    }

    // get AccessControlFilter instance
    public static AccessControlFilter getInstance(AccessControlAllow[] accessControlList)
    {
        if (AccessControlFilter.instance == null) 
            return new AccessControlFilter(accessControlList);
        else 
            return AccessControlFilter.instance;
    }

    // check the class name (allowed or not)
    public void checkAccessControlList(String name) throws AccessException
    {
        // If ACL is null, ACL is disable(all allowable).
        if (allowAccessControlList == null) 
            return;
        
        if (name == null || name.equals(""))
            throw new AccessException("No class name is not allowed to access.");
        String className = lowerCasePackageName(name);
        for (int i = 0; i < allowAccessControlList.length; i++)
        {
            String allowAccessControl = allowAccessControlList[i].getClassName();
            // e.g) *, com.curlap.*, com.curlap.Foo
            if (allowAccessControl.equals("*") ||
                    ((allowAccessControl.endsWith("*") && className.startsWith(allowAccessControl.substring(0, allowAccessControl.length() - 1)))) ||
                    (allowAccessControl.equals(className)))
            {
                return; // allowed
            }
        }
        // warn
        (LogFactory.getLog(AccessControlFilter.class)).warn("Illegal access [" + className + "]");
        throw new AccessException("Illegal access to this object [" + className + "]");
    }
    
    // check the interface's accessible methods and class name (allowed or not)
    public void checkAccessControlList(String name, String methodName, Object[] arguments) throws AccessException
    {
        // If ACL is null, ACL is disable(all allowable).
        if (allowAccessControlList == null) 
            return;
        
        if (name == null || name.equals(""))
            throw new AccessException("No class name is not allowed to access.");
        String className = lowerCasePackageName(name);
        for (int i = 0; i < allowAccessControlList.length; i++)
        {
            String allowAccessControl = allowAccessControlList[i].getClassName();
            // e.g) *, com.curlap.*, com.curlap.Foo
            if (allowAccessControl.equals("*") ||
                    ((allowAccessControl.endsWith("*") && className.startsWith(allowAccessControl.substring(0, allowAccessControl.length() - 1)))) ||
                    (allowAccessControl.equals(className)))
            {
                if (!isAccessibleMethod(allowAccessControlList[i].getInterfaceClass(), methodName, arguments))
                    throw new AccessException("Illegal access to this object [" + className + "][" + methodName + "]");
                return; // allowed
            }
        }
        // warn
        (LogFactory.getLog(AccessControlFilter.class)).warn("Illegal access [" + className + "]");
        throw new AccessException("Illegal access to this object [" + className + "]");
    }

    // change package name to lower case 
    private String lowerCasePackageName(String str)
    {
        int dotPos = str.lastIndexOf(".");
        return (dotPos != -1 ? str.substring(0, dotPos).toLowerCase() + str.substring(dotPos, str.length()) : str);
    }

    // accessible method of interface
    private boolean isAccessibleMethod(Class interfaceClass, String methodName, Object[] args)
    {
        if (interfaceClass == null)
            return true;
        
        Class[] classes = new Class[(args != null ? args.length : 0)];
        for (int i = 0; i < classes.length; i++){
            classes[i] = args[i].getClass();
            if (classes[i].equals(Byte.class))
                classes[i] = byte.class;
            else if (classes[i].equals(Integer.class))
                classes[i] = int.class;
            else if (classes[i].equals(Short.class))
                classes[i] = short.class;
            else if (classes[i].equals(Long.class))
                classes[i] = long.class;
            else if (classes[i].equals(Float.class))
                classes[i] = float.class;
            else if (classes[i].equals(Double.class))
                classes[i] = double.class;
            else if (classes[i].equals(Boolean.class))
                classes[i] = boolean.class;
            else if (classes[i].equals(Character.class))
                classes[i] = char.class;
            else if (classes[i].equals(Integer.class))
                classes[i] = int.class;
        }
        return MethodUtils.getMatchingAccessibleMethod(interfaceClass, methodName, classes) != null;
    }
}
